﻿namespace Jsl.FxCop
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using Microsoft.FxCop.Sdk;

    /// <summary>
    /// A helper class for working with Microsoft FxCop reflection.
    /// </summary>
    public static class NodeHelper
    {
        #region Public Methods
        /// <summary>
        /// Gets the fields for the specified type.
        /// </summary>
        /// <param name="targetType">Type of the target.</param>
        /// <returns>The fields.</returns>
        public static IEnumerable<Field> GetFieldNodes(this Type targetType)
        {
            Contract.Requires(targetType != null);
            Contract.Ensures(Contract.Result<IEnumerable<Field>>() != null);

            return targetType.GetNodeMembers().OfType<Field>();
        }

        /// <summary>
        /// Gets the method.
        /// </summary>
        /// <param name="code">The code to get the <see cref="Method"/> for.</param>
        /// <returns>The method found.</returns>
        public static Method GetMethod(Action code)
        {
            if (code == null)
            {
                throw new ArgumentNullException("code");
            }

            return
                (from method in code.Method.DeclaringType.GetMethodNodes()
                 where method.Name.Name == code.Method.Name
                 select method).Single();
        }

        public static Method GetMethod(this AssemblyNode assemblyNode, string typeName, string methodName)
        {
            Contract.Requires(assemblyNode != null);
            Contract.Requires(!string.IsNullOrWhiteSpace(typeName));
            Contract.Requires(!string.IsNullOrWhiteSpace(methodName));

            return assemblyNode
                .GetTypeNode(typeName)
                .GetMethod(methodName);
        }

        /// <summary>
        /// Gets the single method.
        /// </summary>
        /// <param name="targetType">Type of the target.</param>
        /// <param name="predicate">The predicate to determine the method to return.</param>
        /// <returns>The method.</returns>
        public static Method GetMethod(this TypeNode targetType, string methodName)
        {
            Contract.Requires(targetType != null);

            return targetType
                .Members
                .OfType<Method>()
                .Where(method => method.Name.Name == methodName)
                .FirstOrDefault();
        }

        /// <summary>
        /// Gets the single method.
        /// </summary>
        /// <param name="targetType">Type of the target.</param>
        /// <param name="predicate">The predicate to determine the method to return.</param>
        /// <returns>The method.</returns>
        public static Method GetMethod(this TypeNode targetType, Func<Method, bool> predicate)
        {
            Contract.Requires(targetType != null);
            Contract.Requires(predicate != null);

            return targetType
                .Members
                .OfType<Method>()
                .Where(predicate)
                .FirstOrDefault();
        }

        /// <summary>
        /// Gets the single method.
        /// </summary>
        /// <param name="targetType">Type of the target.</param>
        /// <param name="predicate">The predicate to determine the method to return.</param>
        /// <returns>The method.</returns>
        public static Method GetMethod(this Type targetType, Func<Method, bool> predicate)
        {
            Contract.Requires(targetType != null);
            Contract.Requires(predicate != null);

            return targetType.GetTypeNode().GetMethod(predicate);
        }

        /// <summary>
        /// Gets the method.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="code">The code to get the <see cref="Method"/> for.</param>
        /// <returns>The method found.</returns>
        public static Method GetMethod<TValue>(Func<TValue> code)
        {
            if (code == null)
            {
                throw new ArgumentNullException("code");
            }

            return
                (from method in code.Method.DeclaringType.GetMethodNodes()
                 where method.Name.Name == code.Method.Name
                 select method).Single();
        }

        /// <summary>
        /// Gets the method.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="code">The code to get the <see cref="Method"/> for.</param>
        /// <returns>The method found.</returns>
        public static Method GetMethod<TArg, TValue>(Func<TArg, TValue> code)
        {
            if (code == null)
            {
                throw new ArgumentNullException("code");
            }

            return
                (from method in code.Method.DeclaringType.GetMethodNodes()
                 where method.Name.Name == code.Method.Name
                 select method).Single();
        }

        /// <summary>
        /// Gets the method.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="code">The code to get the <see cref="Method"/> for.</param>
        /// <returns>The method found.</returns>
        public static Method GetMethod<TArg1, TArg2, TValue>(Func<TArg1, TArg2, TValue> code)
        {
            if (code == null)
            {
                throw new ArgumentNullException("code");
            }

            return
                (from method in code.Method.DeclaringType.GetMethodNodes()
                 where method.Name.Name == code.Method.Name
                 select method).Single();
        }

        /// <summary>
        /// Gets the method nodes.
        /// </summary>
        /// <param name="targetType">Type of the target.</param>
        /// <returns>The methods found.</returns>
        public static IEnumerable<Method> GetMethodNodes(this Type targetType)
        {
            Contract.Requires(targetType != null);
            Contract.Ensures(Contract.Result<IEnumerable<Method>>() != null);

            return targetType.GetNodeMembers().OfType<Method>();
        }

        /// <summary>
        /// Gets the property nodes for the specified type.
        /// </summary>
        /// <param name="targetType">Type of the target.</param>
        /// <returns>The property nodes.</returns>
        public static IEnumerable<PropertyNode> GetPropertyNodes(this Type targetType)
        {
            Contract.Requires(targetType != null);
            Contract.Ensures(Contract.Result<IEnumerable<PropertyNode>>() != null);

            return targetType.GetNodeMembers().OfType<PropertyNode>();
        }
        #endregion Public Methods

        #region Private Methods
        private static MemberCollection GetNodeMembers(this Type targetType)
        {
            Contract.Requires(targetType != null);
            Contract.Ensures(Contract.Result<MemberCollection>() != null);

            var targetNode = targetType.GetTypeNode();
            Contract.Assume(targetNode.Members != null);

            return targetNode.Members;
        }
        #endregion Private Methods
    }
}
